type UrlType =
  | string
  | Request
  | Ref<string | Request>
  | (() => string | Request);

export interface RequestOptions {
  method?: any;
  params?: any;
  headers?: any;
  responseType?: "json" | "stream" | "blob";
}

const request = async <T, K>(
  url: UrlType,
  params: K | undefined,
  options: RequestOptions,
  controller?: AbortController
): Promise<{
  error: Error | null;
  data: T | null | ReadableStream | Blob;
}> => {
  let error: Error | null = null;
  const { method = ((options?.method || "GET") as string).toUpperCase() } =
    options;
  try {
    let urlParams: URLSearchParams = new URLSearchParams();
    if (params) {
      urlParams = new URLSearchParams(params!);
    }
    const response = await fetch(
      method === "GET"
        ? `${url}${
            urlParams?.size > 0 ? "?" + new URLSearchParams(options.params) : ""
          }`
        : (url as string),
      {
        method,
        signal: controller?.signal,
        headers: options.headers,
        body: method === "POST" ? JSON.stringify(params) : undefined,
      }
    );
    if (response.status !== 200) {
      error = (await response.json()).error;
      return { error, data: null };
    }
    if (options.responseType === "stream") {
      return { data: response.body, error };
    }
    if (options.responseType === "blob") {
      return { data: await response.blob(), error };
    }
    return { data: await response.json(), error };
  } catch (error: any) {
    return { error, data: null };
  }
};

export const useDefaultRequest = {
  get: <T, K>(url: UrlType, params?: K, option?: RequestOptions) => {
    return request<T, K>(url, params, { method: "GET", ...option });
  },
  post: <T, K>(
    url: UrlType,
    params?: K,
    option?: RequestOptions,
    controller?: AbortController
  ) => {
    return request<T, K>(
      url,
      params,
      { method: "POST", ...option },
      controller
    );
  },
};
